/*
	The MIT License

	Copyright (c) 2010 IFMO/GameDev Studio

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/
// win_inputsys.cpp - mouse and keyboard stuff


#include <queue>
#include "../../core/core.h"
#include <Windows.h>
#include "win_inputsys.h"
#include "win_key_list.h"


/*-----------------------------------------------------------------------------
	EXPORT Stuff
-----------------------------------------------------------------------------*/

EWinInputSystem	is_static;

IInputSystem	*InputSystem	( void ) {
	return &is_static;
}

/*-----------------------------------------------------------------------------
	Input stuff
-----------------------------------------------------------------------------*/

EWinInputSystem	*EWinInputSystem::self		=	NULL;
WNDPROC			EWinInputSystem::OldWndProc	=	NULL;

//
//	EWinInputSystem::EWinInputSystem
//
EWinInputSystem::EWinInputSystem(void)
{
	LOG_INIT("Input system");
	self	=	this;
	
	lua_State *L = CoreLua();
	
	LUALIB_DECLARE	("input")
	LUALIB_METHOD	( bind		 )
	LUALIB_METHOD	( unbind	 )
	LUALIB_METHOD	( unbindAll	 )
	LUALIB_METHOD	( setMouseRelative	 )
	LUALIB_METHOD	( setMouseHide		 )
	LUALIB_METHOD	( setMouseClip		 )
	LUALIB_METHOD	( setKeyboardScan	 )
	LUALIB_REGISTER	( L )
	
	CoreExecuteString("user.input = input;");
	
	lua_pushnil(L);
	state.mouse_func_ref	=	luaL_ref(L, LUA_REGISTRYINDEX);
	
	for (int i=0; i<256; i++) {
		bindlist[i].name		=	OS_KEY_LIST[i];
		bindlist[i].is_pressed	=	false;
		bindlist[i].cmd1		=	"";
	}
	
	InitRawDevices();
}


//
//	EWinInputSystem::~EWinInputSystem
//
EWinInputSystem::~EWinInputSystem(void)
{
	LOG_SHUTDOWN("Input system");
	
	FreeRawInputDevices();

	state.mouse_func	=	"";
	
	SetTargetWindow(NULL);

	ClipCursor( NULL );
	while( ShowCursor(TRUE) < 0 ) ;
}




//
//	EWinInputSystem::SetTargetWindow
//
void EWinInputSystem::SetTargetWindow(void *hwnd)
{
	if (state.target_hwnd) {
		SetWindowLong( (HWND)state.target_hwnd, GWL_WNDPROC, (LONG)(LPVOID)OldWndProc );
	}

	if (hwnd) {
		OldWndProc	=	(WNDPROC) GetWindowLong( (HWND)hwnd, GWL_WNDPROC );
		SetWindowLong( (HWND)hwnd, GWL_WNDPROC, (LONG)(LPVOID)WndProc );
	}

	state.target_hwnd	=	hwnd;
}


//
//	EWinInputSystem::IsTargetWndActive
//
bool EWinInputSystem::IsTargetWndActive(void)
{
	HWND	hWnd	=	(HWND)state.target_hwnd;
	
	if (GetActiveWindow()==hWnd) {
		return true;
	} else {
		return false;
	}
}


//
//	EWinInputSystem::WndProc
//
LRESULT EWinInputSystem::WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	EWinInputSystem	*self	=	(EWinInputSystem*)InputSystem();
	switch (uMsg) {
		case WM_INPUT	:	ProcessWM_INPUTEvent( lParam );		break;
		case WM_CHAR	:	self->EnqueueChar( wParam );		break;
		case WM_KEYDOWN	:	self->EnqueueKey( wParam );			break;
		default			:	return CallWindowProc(OldWndProc, hWnd, uMsg, wParam, lParam );
	}
	return 0;
}


//
//	EWinInputSystem::ProcessInput
//
void EWinInputSystem::ProcessInput(void)
{
	bool	enable_scan =	state.keyboard_scan;
	bool	clip_cursor	=	state.mouse_clip;
	bool	relative	=	state.mouse_relative;
	bool	hide_cursor	=	state.mouse_hide;

	if (!IsTargetWndActive()) {
		enable_scan	= false;
		clip_cursor	= false;
		relative	= false;
		hide_cursor	= false;
	}
	
	
	ScanKeyboard(enable_scan);

	HandleMouse(hide_cursor, clip_cursor, relative);

	//	update script state :
	CoreExecuteString(va("input.mouse	=	{ x=%d; y=%d; dx=%d; dy=%d; }", 
		state.mouse.x,	state.mouse.y,
		state.mouse.dx,	state.mouse.dy	));
		
	CoreExecuteString("input.keyboard	=	{}");
}


//
//	EWinInputSystem::GetMouseInput
//
void EWinInputSystem::GetMouseInput(IInputSystem::MouseInput &mi)
{
	mi = state.mouse;
}


//
//	EWinInputSystem::ScanKeyboard
//
void EWinInputSystem::ScanKeyboard( bool enable_scan )
{
	unsigned char keys[256];
	GetKeyboardState(keys);
	
	for (int i=0; i<256; i++) {
	
		bind_t	*bind =	&bindlist[i];
	
		//	skip wrong key-codes :
		if (!bind->name) continue;
	
		//	skip unassigned values :
		bool	cmd1	=	(bind->cmd1.Length()!=0);
		bool	cmd2	=	(bind->cmd2.Length()!=0);
		
		if (!(cmd1 || cmd2)) {
			continue;
		}
		
		if ((keys[i] & 0x80) && !bind->is_pressed && enable_scan) {
			bind->is_pressed = true;
			if (cmd1) {
				CoreExecuteString(va("%s;", bind->cmd1.CStr()));
			}
		}
		
		if (  ((!(keys[i] & 0x80)) || !enable_scan) && bind->is_pressed) {
			bind->is_pressed = false;
			if (cmd2) {
				CoreExecuteString(va("%s;", bind->cmd2.CStr()));
			}
		}
	}
	
}


//
//	EWinInputSystem::HandleMouse
//
void EWinInputSystem::HandleMouse( bool hide_cursor, bool clip_cursor, bool relative )
{
	HWND	hWnd	=	(HWND)state.target_hwnd;

	if ( hide_cursor ) {
		while( ShowCursor(FALSE) >= 0 ) ;
	} else {
		while( ShowCursor(TRUE) < 0 ) ;
	}

	RECT	rect;
	WINDOWINFO	info;
	GetWindowRect(hWnd, &rect);
	GetWindowInfo(hWnd, &info);

	//	TODO DHG : Is following stuff handle all kind of windows???
	if (info.dwStyle & WS_POPUP) {
	} else {
		rect.left	+= ( GetSystemMetrics(SM_CXFIXEDFRAME) );
		rect.right	-= ( GetSystemMetrics(SM_CXFIXEDFRAME) );
		rect.top	+= ( GetSystemMetrics(SM_CYFIXEDFRAME)+GetSystemMetrics(SM_CYCAPTION) );
		rect.bottom -= ( GetSystemMetrics(SM_CYFIXEDFRAME) );
	}
	int x = rect.left;
	int y = rect.top;

	POINT	pnt;
	GetCursorPos(&pnt);
	int cx	= (rect.left + rect.right)/2;
	int cy	= (rect.top  + rect.bottom)/2;

	if (relative) {
		state.mouse.x		= cx - x;	// at the center of the window
		state.mouse.y		= cy - y;
		state.mouse.dx		= pnt.x - cx;	// 
		state.mouse.dy		= pnt.y - cy;
		
	} else {
		state.mouse.x		= pnt.x - x;
		state.mouse.y		= pnt.y - y;
		state.mouse.dx		= 0;
		state.mouse.dy		= 0;
	}
	
	state.mouse.left	=	(0 != (GetKeyState(VK_LBUTTON) & 0x80));
	state.mouse.middle	=	(0 != (GetKeyState(VK_MBUTTON) & 0x80));
	state.mouse.right	=	(0 != (GetKeyState(VK_RBUTTON) & 0x80));

	if (relative) {
		SetCursorPos(cx, cy);
	}
	if (clip_cursor) {
		ClipCursor(&rect);
	} else {
		ClipCursor(NULL);
	}
}


//
//	EWinInputSystem::SaveKeyBindings
//
void EWinInputSystem::SaveKeyBindings( IHrFile file )
{
	file->printf("\r\n-- key bindings --\r\n\r\n");
	file->printf("input.unbindAll()\r\n");
	
	for (int i=0; i<256; i++)
	{
		if (bindlist[i].name) { 
			EString cmd1	=	bindlist[i].cmd1;
			EString cmd2	=	bindlist[i].cmd2;
			
			if (cmd1=="" && cmd2=="") {
				continue;
			}
			
			file->printf("input.bind (\"%s\", \"%s\", \"%s\");\r\n", bindlist[i].name, cmd1.CStr(), cmd2.CStr());
		}
	}
}


//
//	EWinInputSystem::GetKeyByCmd
//	
uint EWinInputSystem::GetKeyByCmd( const char *cmd )
{	
	for(int i = 0; i < 256; i++) {		
		if ( strcmpi(bindlist[i].cmd1.CStr(), cmd) ==0 ) {
			return i;
		}
	}
	return 0;
}


//
//	EWinInputSystem::SetInputMode
//
void EWinInputSystem::SetInputMode( uint flags )
{
	state.input_mode		=	flags;
	state.mouse_clip		=	( 0!= ( flags & IN_MOUSE_CLIP		) );	
	state.mouse_hide		=	( 0!= ( flags & IN_MOUSE_HIDE		) );
	state.mouse_relative	=	( 0!= ( flags & IN_MOUSE_RELATIVE	) );
	state.keyboard_scan		=	( 0!= ( flags & IN_KB_SCAN			) );
}

/*-----------------------------------------------------------------------------
	Stuff...
-----------------------------------------------------------------------------*/

//
//	EWinInputSystem::bind
//
int EWinInputSystem::bind(lua_State *L)
{
	LUA_INTERFACE(L);
	EWinInputSystem	*self = static_cast<EWinInputSystem*>(InputSystem());

	int n = lua_gettop(L);
	
	EString	key		=	lua.RequireString(1, "key");
	EString cmd1	=	lua.RequireString(2, "command 1");
	EString cmd2	=	"";
	if (n==3) cmd2	=	lua.RequireString(3, "command 2");

	uint key_id	=	self->GetKeyCode(key.CStr());
	if (key_id==0) RUNTIME_ERROR(va("unknown key \"%s\"", key.CStr()));
	
	self->bindlist[key_id].cmd1	=	cmd1;
	self->bindlist[key_id].cmd2	=	cmd2;

	return 0;
}


//
//	EWinInputSystem::unbind
//
int EWinInputSystem::unbind(lua_State *L)
{
	LUA_INTERFACE(L);
	EWinInputSystem	*self = static_cast<EWinInputSystem*>(InputSystem());

	int n = lua_gettop(L);
	
	EString	key	=	lua.RequireString(1, "key");

	uint key_id	=	self->GetKeyCode(key.CStr());
	if (key_id==0) RUNTIME_ERROR(va("unknown key \"%s\"", key.CStr()));
	
	self->bindlist[key_id].cmd1	=	"";
	self->bindlist[key_id].cmd2	=	"";

	return 0;
}


//
//	EWinInputSystem::unbindAll
//
int EWinInputSystem::unbindAll(lua_State *L)
{
	LUA_INTERFACE(L);
	EWinInputSystem	*self = static_cast<EWinInputSystem*>(InputSystem());

	for (uint i=0; i<256; i++) {
		self->bindlist[i].cmd1 = "";
		self->bindlist[i].cmd2 = "";
	}
	
	return 0;
}


//
//	EWinInputSystem::GetKeyCode
//
uint EWinInputSystem::GetKeyCode(const char *name)
{
	for (uint i=0; i<256; i++)
	{
		if ( bindlist[i].name ) {
			if ( strcmpi(bindlist[i].name, name)==0) return i;
		}
	}
	
	return 0;
}


//
//	EWinInputSystem::setMouseRelative
//
int EWinInputSystem::setMouseRelative( lua_State *L )
{
	EWinInputSystem	*self		=	static_cast<EWinInputSystem*>(InputSystem());
	self->state.mouse_relative	=	lua_toboolean(L, 1)!=0;
	return 0;
}


//
//	EWinInputSystem::setMouseHide
//
int EWinInputSystem::setMouseHide( lua_State *L )
{
	EWinInputSystem	*self	=	static_cast<EWinInputSystem*>(InputSystem());
	self->state.mouse_hide	=	lua_toboolean(L, 1)!=0;
	return 0;
}


//
//	EWinInputSystem::setMouseClip
//
int EWinInputSystem::setMouseClip( lua_State *L )
{
	EWinInputSystem	*self	=	static_cast<EWinInputSystem*>(InputSystem());
	self->state.mouse_clip	=	lua_toboolean(L, 1)!=0;
	return 0;
}


//
//	EWinInputSystem::setKeyboardScan
//
int EWinInputSystem::setKeyboardScan( lua_State *L )
{
	EWinInputSystem	*self		=	static_cast<EWinInputSystem*>(InputSystem());
	self->state.keyboard_scan	=	lua_toboolean(L, 1)!=0;
	return 0;
}


//
//	EWinInputSystem::GetKbChar
//
char EWinInputSystem::GetKbChar( void )
{
	if (kbchars.empty()) {
		return 0;
	}
	char ch = kbchars.front();
	kbchars.pop();
	return ch;
}


//
//	EWinInputSystem::GetKbKey
//
char EWinInputSystem::GetKbKey( void )
{
	if (kbkeys.empty()) {
		return 0;
	}
	char key = kbkeys.front();
	kbkeys.pop();
	return key;
}


//
//	EWinInputSystem::EnqueueChar
//
void EWinInputSystem::EnqueueChar( char ch )
{
	if (!state.keyboard_scan) {
		kbchars.push(ch); 
	}
}


//
//	EWinInputSystem::EnqueueKey
//
void EWinInputSystem::EnqueueKey( char key )
{
	if (!state.keyboard_scan) {
		kbkeys.push(key); 
	}
}


//
//	EWinInputSystem::IsKeyPressed
//
bool EWinInputSystem::IsKeyPressed( uint code )
{
	if ( GetKeyState( code ) & 0x80 ) {
		return true;
	} else {
		return false;
	}
}


//
//	EWinInputSystem::GetS3DMouseInput
//
void EWinInputSystem::GetS3DMouseInput( S3DMouseInput *mi )
{
	*mi	=	state.s3dmin;
}